<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Prudynt-T WebRTC Client</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            background-color: #f5f5f5;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        .header {
            text-align: center;
            margin-bottom: 30px;
        }
        .controls {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
            flex-wrap: wrap;
        }
        .control-group {
            display: flex;
            flex-direction: column;
            gap: 5px;
        }
        .control-group label {
            font-weight: bold;
            font-size: 12px;
        }
        input, button, select {
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        button {
            background-color: #007bff;
            color: white;
            cursor: pointer;
            border: none;
        }
        button:hover {
            background-color: #0056b3;
        }
        button:disabled {
            background-color: #6c757d;
            cursor: not-allowed;
        }
        .video-container {
            display: flex;
            gap: 20px;
            margin-bottom: 20px;
            flex-wrap: wrap;
        }
        .video-wrapper {
            flex: 1;
            min-width: 300px;
        }
        video {
            width: 100%;
            height: auto;
            background-color: #000;
            border-radius: 4px;
        }
        .status {
            padding: 10px;
            border-radius: 4px;
            margin-bottom: 20px;
            font-weight: bold;
        }
        .status.disconnected {
            background-color: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }
        .status.connecting {
            background-color: #fff3cd;
            color: #856404;
            border: 1px solid #ffeaa7;
        }
        .status.connected {
            background-color: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }
        .logs {
            background-color: #f8f9fa;
            border: 1px solid #dee2e6;
            border-radius: 4px;
            padding: 10px;
            height: 200px;
            overflow-y: auto;
            font-family: monospace;
            font-size: 12px;
        }
        .log-entry {
            margin-bottom: 5px;
        }
        .log-entry.error {
            color: #dc3545;
        }
        .log-entry.info {
            color: #007bff;
        }
        .log-entry.debug {
            color: #6c757d;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>Prudynt-T WebRTC Client</h1>
            <p>Real-time video streaming from Thingino camera</p>
        </div>

        <div class="controls">
            <div class="control-group">
                <label>Camera IP:</label>
                <input type="text" id="cameraIp" value="192.168.1.109" placeholder="Camera IP address">
            </div>
            <div class="control-group">
                <label>WebSocket Port:</label>
                <input type="number" id="wsPort" value="8089" placeholder="WebSocket port">
            </div>
            <div class="control-group">
                <label>Token:</label>
                <input type="text" id="token" placeholder="Authentication token (optional)" value="a1b2c3d4e5f67890abcdef1234567890">
            </div>
            <div class="control-group">
                <label>Stream:</label>
                <select id="streamSelect">
                    <option value="ch0">Main Stream (ch0)</option>
                    <option value="ch1">Sub Stream (ch1)</option>
                </select>
            </div>
            <div class="control-group">
                <label>Actions:</label>
                <button id="connectBtn" onclick="connect()">Connect</button>
                <button id="disconnectBtn" onclick="disconnect()" disabled>Disconnect</button>
                <button onclick="clearLogs()">Clear Logs</button>
            </div>
            <div class="control-group">
                <label>Data Channels:</label>
                <button id="pingBtn" onclick="sendPing()" disabled>Send Ping</button>
                <button id="statsBtn" onclick="requestStatistics()" disabled>Get Stats</button>
            </div>
        </div>

        <div id="status" class="status disconnected">
            Status: Disconnected
        </div>

        <div class="video-container">
            <div class="video-wrapper">
                <h3>Remote Video Stream</h3>
                <video id="remoteVideo" autoplay muted playsinline></video>
            </div>
        </div>

        <div>
            <h3>Connection Logs</h3>
            <div id="logs" class="logs"></div>
        </div>
    </div>

    <script>
        let ws = null;
        let pc = null;
        let isConnected = false;
        let dataChannels = {};

        // WebRTC configuration - Force real IP candidates
        const rtcConfig = {
            iceServers: [
                { urls: 'stun:stun.l.google.com:19302' },
                { urls: 'stun:stun1.l.google.com:19302' }
            ],
            iceCandidatePoolSize: 10,
            bundlePolicy: 'balanced',
            rtcpMuxPolicy: 'require',
            // Force Chrome to expose local IPs
            iceTransportPolicy: 'all'
        };

        function log(message, type = 'info') {
            const logs = document.getElementById('logs');
            const entry = document.createElement('div');
            entry.className = `log-entry ${type}`;
            entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
            logs.appendChild(entry);
            logs.scrollTop = logs.scrollHeight;
            console.log(`[${type.toUpperCase()}] ${message}`);
        }

        function updateStatus(status, className) {
            const statusEl = document.getElementById('status');
            statusEl.textContent = `Status: ${status}`;
            statusEl.className = `status ${className}`;
        }

        function updateButtons(connected) {
            document.getElementById('connectBtn').disabled = connected;
            document.getElementById('disconnectBtn').disabled = !connected;
            document.getElementById('pingBtn').disabled = !connected;
            document.getElementById('statsBtn').disabled = !connected;
        }

        async function connect() {
            const ip = document.getElementById('cameraIp').value;
            const port = document.getElementById('wsPort').value;
            const token = document.getElementById('token').value;

            if (!ip) {
                log('Please enter camera IP address', 'error');
                return;
            }

            try {
                updateStatus('Connecting...', 'connecting');
                updateButtons(true);

                // Build WebSocket URL
                let wsUrl = `ws://${ip}:${port}/`;
                if (token) {
                    wsUrl += `?token=${encodeURIComponent(token)}`;
                }

                log(`Connecting to ${wsUrl}`);

                // Create WebSocket connection
                ws = new WebSocket(wsUrl);

                ws.onopen = async () => {
                    log('WebSocket connected');
                    await initWebRTC();
                };

                ws.onmessage = async (event) => {
                    // Debug: Log raw message before parsing
                    console.log('Raw message received:', event.data);
                    log(`Raw message length: ${event.data.length} chars`);

                    try {
                        const message = JSON.parse(event.data);
                        // Handle double-nested webrtc structure from server
                        if (message.webrtc && message.webrtc.webrtc) {
                            await handleSignalingMessage(message.webrtc.webrtc);
                        } else if (message.webrtc) {
                            await handleSignalingMessage(message.webrtc);
                        } else {
                            log('No webrtc object in message', 'error');
                        }
                    } catch (error) {
                        log(`Error parsing WebSocket message: ${error.message}`, 'error');
                        log(`Raw message that failed: ${event.data}`, 'error');
                        console.error('Raw message that failed:', event.data);
                    }
                };

                ws.onclose = () => {
                    log('WebSocket disconnected');
                    cleanup();
                };

                ws.onerror = (error) => {
                    log(`WebSocket error: ${error}`, 'error');
                    cleanup();
                };

            } catch (error) {
                log(`Connection error: ${error.message}`, 'error');
                cleanup();
            }
        }

        async function initWebRTC() {
            try {
                log('Initializing WebRTC peer connection');

                // Create peer connection
                pc = new RTCPeerConnection(rtcConfig);

                // Add transceiver for video to request video stream from server
                pc.addTransceiver('video', { direction: 'recvonly' });

                // Audio is disabled in the streamer, so we don't request audio
                // pc.addTransceiver('audio', { direction: 'recvonly' });

                // Handle ICE candidates
                pc.onicecandidate = (event) => {
                    if (event.candidate) {
                        // Debug: Log the actual ICE candidate
                        console.log('Browser ICE candidate:', event.candidate.candidate);
                        log(`ICE candidate: ${event.candidate.candidate.substring(0, 50)}...`);

                        sendSignalingMessage({
                            type: 'ice-candidate',
                            candidate: event.candidate.candidate,
                            sdpMid: event.candidate.sdpMLineIndex
                        });
                    }
                };

                // Handle remote stream
                pc.ontrack = (event) => {
                    log('Received remote stream');
                    const remoteVideo = document.getElementById('remoteVideo');
                    remoteVideo.srcObject = event.streams[0];
                    updateStatus('Connected - Streaming', 'connected');
                    isConnected = true;
                };

                // Handle connection state changes
                pc.onconnectionstatechange = () => {
                    log(`Connection state: ${pc.connectionState}`);
                    if (pc.connectionState === 'failed' || pc.connectionState === 'disconnected') {
                        cleanup();
                    }
                };

                // Handle incoming data channels
                pc.ondatachannel = (event) => {
                    const channel = event.channel;
                    log(`Received data channel: ${channel.label}`);
                    setupDataChannel(channel);
                };

                // Create data channels
                createDataChannels();

                // Create offer
                log('Creating WebRTC offer');
                const offer = await pc.createOffer();
                await pc.setLocalDescription(offer);

                // Debug: Log the offer SDP to see media line order
                console.log('Client offer SDP:', offer.sdp);
                log('Client offer created - check console for SDP details');

                // Send offer to camera
                sendSignalingMessage({
                    type: 'offer',
                    sdp: offer.sdp
                });

            } catch (error) {
                log(`WebRTC initialization error: ${error.message}`, 'error');
                cleanup();
            }
        }

        async function handleSignalingMessage(message) {
            log(`Received signaling message: ${message.type}`);

            try {
                switch (message.type) {
                    case 'offer':
                        // Server acknowledgment of our offer
                        if (message.success) {
                            log(`Server acknowledged offer: ${message.message}`);
                        } else {
                            log(`Server rejected offer: ${message.message}`, 'error');
                        }
                        break;

                    case 'answer':
                        if (message.sdp) {
                            await pc.setRemoteDescription({
                                type: 'answer',
                                sdp: message.sdp
                            });
                            log('Set remote description (answer)');
                        } else if (message.success) {
                            log(`Server acknowledged answer: ${message.message}`);
                        }
                        break;

                    case 'answer':
                        if (message.sdp) {
                            await pc.setRemoteDescription({
                                type: 'answer',
                                sdp: message.sdp
                            });
                            log('Set remote description (answer)');
                        } else if (message.success) {
                            log(`Server acknowledged answer: ${message.message}`);
                        }
                        break;

                    case 'ice-candidate':
                        if (message.candidate) {
                            await pc.addIceCandidate({
                                candidate: message.candidate,
                                sdpMLineIndex: message.sdpMid
                            });
                            log('Added ICE candidate');
                        } else if (message.success) {
                            log(`Server acknowledged ICE candidate: ${message.message}`);
                        }
                        break;

                    case 'error':
                        log(`Server error: ${message.message}`, 'error');
                        break;

                    default:
                        log(`Unknown message type: ${message.type}`, 'debug');
                }
            } catch (error) {
                log(`Error handling signaling message: ${error.message}`, 'error');
            }
        }

        function sendSignalingMessage(message) {
            if (ws && ws.readyState === WebSocket.OPEN) {
                const webrtcMessage = {
                    webrtc: message
                };
                ws.send(JSON.stringify(webrtcMessage));
                log(`Sent: ${message.type}`);
            } else {
                log('WebSocket not connected', 'error');
            }
        }

        function disconnect() {
            log('Disconnecting...');
            cleanup();
        }

        function cleanup() {
            isConnected = false;

            if (pc) {
                pc.close();
                pc = null;
            }

            if (ws) {
                ws.close();
                ws = null;
            }

            const remoteVideo = document.getElementById('remoteVideo');
            remoteVideo.srcObject = null;

            updateStatus('Disconnected', 'disconnected');
            updateButtons(false);
            log('Disconnected and cleaned up');
        }

        function createDataChannels() {
            try {
                // Create control data channel
                const controlChannel = pc.createDataChannel('control', {
                    ordered: true
                });
                setupDataChannel(controlChannel);

                // Create statistics data channel
                const statsChannel = pc.createDataChannel('statistics', {
                    ordered: true
                });
                setupDataChannel(statsChannel);

                log('Data channels created');
            } catch (error) {
                log(`Error creating data channels: ${error.message}`, 'error');
            }
        }

        function setupDataChannel(channel) {
            dataChannels[channel.label] = channel;

            channel.onopen = () => {
                log(`Data channel '${channel.label}' opened`);

                // Send initial ping on control channel
                if (channel.label === 'control') {
                    sendDataChannelMessage('control', {
                        command: 'ping'
                    });
                }

                // Request statistics on statistics channel
                if (channel.label === 'statistics') {
                    sendDataChannelMessage('statistics', {
                        command: 'get_stats'
                    });
                }
            };

            channel.onclose = () => {
                log(`Data channel '${channel.label}' closed`);
                delete dataChannels[channel.label];
            };

            channel.onerror = (error) => {
                log(`Data channel '${channel.label}' error: ${error}`, 'error');
            };

            channel.onmessage = (event) => {
                handleDataChannelMessage(channel.label, event.data);
            };
        }

        function sendDataChannelMessage(channelLabel, message) {
            const channel = dataChannels[channelLabel];
            if (!channel || channel.readyState !== 'open') {
                log(`Data channel '${channelLabel}' not available`, 'error');
                return false;
            }

            try {
                const messageStr = typeof message === 'string' ? message : JSON.stringify(message);
                channel.send(messageStr);
                log(`Sent to '${channelLabel}': ${messageStr}`);
                return true;
            } catch (error) {
                log(`Error sending to '${channelLabel}': ${error.message}`, 'error');
                return false;
            }
        }

        function handleDataChannelMessage(channelLabel, message) {
            log(`Received from '${channelLabel}': ${message}`);

            try {
                const data = JSON.parse(message);

                if (channelLabel === 'control') {
                    handleControlMessage(data);
                } else if (channelLabel === 'statistics') {
                    handleStatisticsMessage(data);
                }
            } catch (error) {
                log(`Error parsing message from '${channelLabel}': ${error.message}`, 'error');
            }
        }

        function handleControlMessage(data) {
            if (data.response === 'pong') {
                log(`Ping response: ${data.timestamp}ms`);
            } else if (data.response === 'status') {
                log(`Status: connected=${data.connected}, session=${data.session}`);
            } else if (data.error) {
                log(`Control error: ${data.error}`, 'error');
            }
        }

        function handleStatisticsMessage(data) {
            if (data.session_id) {
                log(`Statistics: Connection time: ${data.connection_time_seconds}s, ` +
                    `Video: ${data.video_enabled}, Audio: ${data.audio_enabled}, ` +
                    `Data channels: ${data.data_channels_count}`, 'info');
            }
        }

        function sendPing() {
            sendDataChannelMessage('control', {
                command: 'ping'
            });
        }

        function requestStatistics() {
            sendDataChannelMessage('statistics', {
                command: 'get_stats'
            });
        }

        function clearLogs() {
            document.getElementById('logs').innerHTML = '';
        }

        // Initialize UI
        updateButtons(false);
        log('WebRTC client initialized');

        // Add periodic statistics request
        setInterval(() => {
            if (isConnected && dataChannels['statistics']) {
                requestStatistics();
            }
        }, 10000); // Every 10 seconds
    </script>
</body>
</html>
